package com.canyou.handler;

import com.canyou.api.ResponseBO;
import com.canyou.enums.ErrorCodeEnum;
import com.canyou.exception.CysBusinessException;
import com.canyou.utils.SpringContextUtils;
import com.fasterxml.jackson.core.JsonParseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;

import static com.canyou.api.ResponseBO.*;

/***
 * 全局的各种控制器异常拦截器: 包括 @PathVariable 和 @RequestParam 参数校验错误处理, @RequestBody的bean 校验错误处理
 * 自定义业务异常, controller之上的未处理异常Exception
 *
 * @author fy
 * @date 2018-07-13 19:09
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {

    @Autowired
    private SpringContextUtils springContextUtils;

    /**
     * bean @RequestBody参数校验失败
     * 自定义 bean 校验错误处理
     * fail_fast模式下是抛出 MethodArgumentNotValidException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})
    public ResponseBO handleBindException(Exception e) {
        log.warn("bean校验错误: {}", e.getMessage(), e);
        BindingResult bindingResult;
        if (e instanceof BindException) {
            bindingResult = ((BindException) e).getBindingResult();
        } else {
            bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
        }
        StringBuilder strBuilder = new StringBuilder();
        if (bindingResult.hasErrors()) {
            List<ObjectError> errorList = bindingResult.getAllErrors();
            for (ObjectError oe : errorList) {
                if (oe instanceof FieldError) {
                    String field = ((FieldError) oe).getField();
                    String error = oe.getDefaultMessage();
                    strBuilder.append(field).append(":").append(error).append("\n");
                } else {
                    strBuilder.append(oe.getObjectName()).append(":").append(oe.getDefaultMessage()).append("\n");
                }
            }
        }
        String msg = strBuilder.substring(0, strBuilder.length() - 1);
        return illegal(msg);
    }

    /**
     * 处理 @RequestBody 时,json解析出错
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = {HttpMessageConversionException.class, JsonParseException.class})
    public ResponseBO handleJsonException(Exception e) {
        log.warn("处理RequestBody时,json解析出错: {}", e.getMessage(), e);
        if (springContextUtils.isPrd()) {
            return error(ErrorCodeEnum.ILLEGAL);
        }
        return illegal(e.getMessage());
    }

    /**
     * 自定义@PathVariable 和 @RequestParam 参数校验错误处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseBO handleConstraintViolationException(ConstraintViolationException e) {
        log.warn("参数校验错误: {}", e.getMessage(), e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        StringBuilder strBuilder = new StringBuilder();
        for (ConstraintViolation<?> violation : violations) {
            strBuilder.append(violation.getMessage() + "\n");
        }
        String msg = strBuilder.substring(0, strBuilder.length() - 1);
        return illegal(msg);
    }

    /**
     * 参数 @RequestParam 缺失异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    public ResponseBO handleRuntimeException(MissingServletRequestParameterException e) {
        log.warn("@RequestParam参数缺失异常: {}", e.getMessage(), e);
        // e.getParameterType() + "类型" + e.getParameterName() + "不能为空"
        return illegal(e.getParameterName() + "不能为空");
    }

    /**
     * 处理当前服务模块抛出的可处理异常,如参数和业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = {CysBusinessException.class})
    public ResponseBO handleRuntimeException(CysBusinessException e) {
        log.warn("自定义业务异常: {}", e.getMessage(), e);
        return fail(e.getCode(), e.getMsg());
    }

    /**
     * 访问没有权限
     *
     * @param e
     * @return
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseBO handleAccessDeniedException(AccessDeniedException e) {
        log.warn("访问没有权限: {}", e.getMessage(), e);
        return fail(e.getMessage());
    }

    /**
     * 数据库执行异常, 统一返回1500.
     *
     * @param e
     * @return
     */
    @ExceptionHandler(DataAccessException.class)
    public ResponseBO handleDataAccessException(DataAccessException e) {
        log.error("数据库执行异常: {}", e.getMessage(), e);
        if (springContextUtils.isPrd()) {
            return error();
        }
        return fail(e.getMessage());
    }

    /**
     * 其他未处理异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public ResponseBO handleException(Exception e) {
        log.error("其他未处理异常: {}", e.getMessage(), e);
        if (springContextUtils.isPrd()) {
            return error();
        }
        return fail(e.getMessage());
    }

}
